Skip to content

Conversation

@DokPlay
Copy link
Owner

@DokPlay DokPlay commented Aug 18, 2025

Fix

  • Почистил комментарии по проекту: упростил формулировки, убрал «украшения», оставил только смысл.

  • Вынес общие хелперы HTTP: HttpUtil.isNewId, HttpUtil.parseIdOrNull; удалил дубли приватных методов в хендлерах.

  • Хендлеры (TasksHandler, SubtasksHandler, EpicsHandler):

    • убраны локальные get/post/delete/firstId.../client,
    • перешёл на import static http.TestHttpUtils.* в тестах,
    • ловим NotFoundException → возвращаем 404, пересечения времени → 406.
  • Менеджер (InMemoryTaskManager):

    • get*/update*/remove* кидают NotFoundException,
    • addNewSubtask кидает NotFoundException("epic X not found"),
    • фиксы приоритезации: переиндексация и удаление по id.
  • FileBackedTaskManager:

    • CSV расширен: durationMinutes, startTime (ISO),
    • восстановление через put*PreserveId + setNextIdAfterRestore(maxId+1).
  • Тесты:

    • перенёс всё в src/test/java, удалил старые пути,
    • обновил HTTP-тесты под новые хелперы и 404/406,
    • базовый тест BaseHttpTest поддерживает внешний сервер через -DBASE_URL.

TestHttpUtils (тестовые хелперы)

  • Единая обёртка над HttpClient для тестов: меньше шума и дублирования.

  • Методы (static, импортируются через import static http.TestHttpUtils.*):

    • get(baseUrl, path)
    • post(baseUrl, path, json) — ставит Content-Type: application/json
    • delete(baseUrl, path)
    • firstIdFromArray(jsonArrayString) — быстрый парс первого id из JSON-массива.
  • Не завязан на сервере/менеджере, работает только по HTTP; baseUrl берётся из BaseHttpTest.

  • Результат — компактные и читаемые тесты без локальных дубликатов get/post/delete.

Поведение API (кратко)

  • 404 Not Found — если сущность с {id} не существует (для GET/POST(обновление)/DELETE на /tasks|/epics|/subtasks/{id}).
  • 406 Not Acceptable — при пересечении по времени для POST/обновления tasks|subtasks.
  • Формат данных:
    startTime — ISO yyyy-MM-dd'T'HH:mm[:ss],
    duration — минуты (целое число).

Проверка “пересечения” (Windows cmd.exe)

REM 10:00–10:10
curl -i -X POST http://localhost:8080/tasks ^
 -H "Content-Type: application/json" ^
 -d "{\"title\":\"T1\",\"description\":\"\",\"status\":\"NEW\",\"startTime\":\"2035-01-01T10:00:00\",\"duration\":10}"

REM попытка наложиться 10:05–10:20 → ожидаем 406
curl -i -X POST http://localhost:8080/tasks ^
 -H "Content-Type: application/json" ^
 -d "{\"title\":\"Clash\",\"description\":\"\",\"status\":\"NEW\",\"startTime\":\"2035-01-01T10:05:00\",\"duration\":15}"

полезно для контроля:

curl -s http://localhost:8080/prioritized
curl -s http://localhost:8080/tasks

Зачем

  • меньше дублирования кода,
  • явная семантика ошибок на уровне менеджера,
  • чище и короче тесты, единый стиль комментариев.

Sprint 9: HTTP API for Kanban Tracker

Цель: открыть функциональность TaskManager по HTTP. Пользователь может работать с задачами, подзадачами, эпиками, историей и приоритезированным списком через REST.

Что сделано

  • Добавлен HTTP-сервер HttpTaskServer (порт по умолчанию 8080, поддержка запуска на произвольном порту в тестах).

  • Хендлеры:

    • /tasksGET, POST, DELETE /{id}
    • /subtasksGET, POST, DELETE /{id}
    • /epicsGET, POST, DELETE /{id}, а также GET /epics/{id}/subtasks
    • /historyGET
    • /prioritizedGET
  • Базовый класс хендлеров BaseHttpHandler с единообразной отправкой ответов: 200/201/404/406/500.

  • JSON через Gson (library/gson-2.11.0.jar в репозитории). Адаптеры:

    • LocalDateTime ↔ ISO-строка (yyyy-MM-dd'T'HH:mm:ss)
    • Duration ↔ миллисекунды
  • Улучшения менеджера:

    • устранены дубли в /prioritized при add/update/remove;
    • корректная очистка при удалении задач/сабов/эпиков; пересчёт эпиков.
  • Модели:

    • Epic — безопасная работа со списком сабтасков, пересчёт статуса/времени/длительности;
    • Subtask — удобные конструкторы с Duration и LocalDateTime.
  • Тестовая инфраструктура:

    • BaseHttpTest — автоподнятие локального сервера на свободном порту (0) + поддержка -DBASE_URL=http://localhost:8080 для прогона против внешнего инстанса.
    • Интеграционные тесты для всех эндпоинтов, включая проверку 406 на пересечения и уникальность в /prioritized.

Карта API

  • Задачи

    • GET /tasks — список
    • GET /tasks/{id} — по id
    • POST /tasks — создать/обновить (если id отсутствует — create; иначе update)
    • DELETE /tasks/{id} — удалить
  • Подзадачи

    • GET /subtasks
    • GET /subtasks/{id}
    • POST /subtasks
    • DELETE /subtasks/{id}
  • Эпики

    • GET /epics
    • GET /epics/{id}
    • POST /epics
    • DELETE /epics/{id}
    • GET /epics/{id}/subtasks — все сабтаски эпика
  • История/приоритет

    • GET /history
    • GET /prioritized

Коды ответа

  • 200 OK — успешный запрос с телом
  • 201 Created — успешный POST без сущности в ответе
  • 404 Not Found — объект или путь не найдены
  • 406 Not Acceptable — пересечение по времени
  • 500 Internal Server Error — иные ошибки обработки

Примеры запросов (curl)

# создать задачу
curl -X POST http://localhost:8080/tasks \
  -H "Content-Type: application/json" \
  -d '{"name":"Demo","description":"via HTTP","status":"NEW","duration":600000,"startTime":"2035-01-01T10:00:00"}'

# получить все эпики
curl http://localhost:8080/epics

# сабтаски эпика 1
curl http://localhost:8080/epics/1/subtasks

Как запускать

  • Из IDE: http.HttpTaskServer.main().
    Файл tasks.csv создаётся при первом изменении данных и пишется в Working directory.

  • Тесты:

    • по умолчанию поднимается временный сервер на порту 0 (чистое окружение);

    • для прогона против запущенного инстанса добавь VM-опцию:

      -DBASE_URL=http://localhost:8080
      

Замечания по совместимости

  • Изменений, ломающих формат CSV, нет; FileBackedTaskManager продолжает работать.
  • Авторизация/конкурентный доступ не реализованы — планируется отдельно.

Проверено

  • Ручные проверки curl/Postman.
  • Интеграционные тесты для всех эндпоинтов: CRUD-сценарии, ошибки 404/406, сортировка /prioritized, заполнение /history.

Copy link

@LexLippi LexLippi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Привет!

Неплохая работа!

Есть некоторое количество замечаний, которые необходимо исправить!

Желаю удачи!

Comment on lines 85 to 110
/* ===== helpers ===== */

private HttpResponse<String> get(String path) throws IOException, InterruptedException {
return client()
.send(
HttpRequest.newBuilder(URI.create(baseUrl + path)).GET().build(),
HttpResponse.BodyHandlers.ofString());
}

private HttpResponse<String> post(String path, String body)
throws IOException, InterruptedException {
return client()
.send(
HttpRequest.newBuilder(URI.create(baseUrl + path))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build(),
HttpResponse.BodyHandlers.ofString());
}

private HttpResponse<String> delete(String path) throws IOException, InterruptedException {
return client()
.send(
HttpRequest.newBuilder(URI.create(baseUrl + path)).DELETE().build(),
HttpResponse.BodyHandlers.ofString());
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Эти хелперы очень похожи лучше выгести их в отдельный утилитарный класс, чтобы не копипастить

Comment on lines 112 to 120
private static int firstIdFromArray(String json) {
JsonArray arr = JsonParser.parseString(json).getAsJsonArray();
JsonObject o = arr.get(0).getAsJsonObject();
return o.get("id").getAsInt();
}

private static HttpClient client() {
return HttpClient.newHttpClient();
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Аналогично эти методы тоже хочется куда-то вынести, чтобы не копипастить

dur,
st,
"" // epic
protected int id;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Лучше не совмещать такие коммиты в одном PR.
Часто в рабочих проектах есть отдельные задачи на форматирование кода и их не объединяют с другими, чтобы не захламлять осмысленные PR украшательствами всех файлов проекта, потому что в случае необходимости откатить изменения нам придется откатывать много чего лишнего (что может сказаться на скорости)

return id == null || id == 0;
}

private Integer parseId(String s) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Этот метод может быть статическим и лучше вынести его в отдельный класс с утилитами
  2. Стоит назвать его поконкретнее, например parseIdWithoutException

sendOk(exchange, "\"deleted\"");
}

private boolean isNewId(Integer id) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Аналогичное замечание, как и к методу ниже

manager.addNewTask(incoming);
sendCreated(exchange);
} else {
// sprint9: обновляем ТОЛЬКО существующую задачу — иначе 404

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Эту логику лучше не накладывать на хэндлер, а имплементировать ее внутри менеджера. Например, выбрасывать исключение, если не нашли обновляемую задачу, а здесь это исключение перебрасывать в sendNotFound

Comment on lines 52 to 56
if (task == null) {
sendNotFound(exchange, "task " + id + " not found");
} else {
sendOk(exchange, gson.toJson(task));
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Аналогично, лучше такую логику реализовывать на уровне менеджера

Comment on lines 117 to 127
private boolean isNewId(Integer id) {
return id == null || id == 0;
}

private Integer parseId(String s) {
try {
return Integer.parseInt(s);
} catch (NumberFormatException e) {
return null;
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

про это уже писал

Copy link

@LexLippi LexLippi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Привет!

Хорошая работа!

Желаю удачи в следующих спринтах!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants